// * setTimeout 和 setInterval 所有浏览器和Node.js都支持这两个方法。

// * setTimeout 语法：let timerId = setTimeout(func|code, [delay], [arg1], [arg2], ...)
// func|code 要执行的函数或代码字符串，一般都是函数，历史原因支持字符串，但不建议这样做。
// delay 执行前的延时，单位毫秒。默认值是 0。
// arg1, arg2, ... 传递给 func|code 的参数。

// 完整示例：
function sayHi(phrase, who) {
	console.log(phrase + ', ' + who)
}

// timerId 浏览器环境返回的是一个数字。Node.js 返回的是一个定时器对象，这个对象包含一系列方法。
let timerId = setTimeout(sayHi, 1000, 'Hello', 'World')

// 取消调度
clearTimeout(timerId)

// *********************************************************************************************

// * setInterval 语法：let timerId = setInterval(func|code, [delay], [arg1], [arg2], ...)

// 在大多数浏览器中，包括 Chrome 和 Firefox，在显示 alert/confirm/prompt 弹窗时，内部的定时器仍旧会继续“嘀嗒”。

// 完整示例：
function sayHi2(phrase, who) {
	console.log(phrase + ', ' + who)
}

let timerId2 = setInterval(sayHi2, 1000, 'Hello', 'World')

setTimeout(() => clearInterval(timerId2), 500) // 取消调度（还没等到1s后，调度就被取消了）

// *********************************************************************************************

// * 嵌套的 setTimeout（周期性调度）

let timerId3 = setTimeout(function tick() {
	console.log(tick.name)
	timerId3 = setTimeout(tick, 1000)
}, 1000)

setTimeout(() => clearTimeout(timerId3), 5000) // 5s后取消调度

// * 嵌套调用使用场景例子：
// 每 5s 向服务器发送一次请求，但如果服务器过载了，那么就要降低请求频率，比如增加间隔时间 10s 20 40 等。

// 伪代码：
let prams = { name: 'John', age: 30 }

let delay = 5000

let timerId4 = setTimeout(
	function request(prams) {
		// 发送请求(prams)
		console.log(prams)

		if (true /* 由于服务器过载，请求失败 */) {
			delay *= 2
		}

		timerId4 = setTimeout(request, delay, prams)
	},
	delay,
	prams
)

// 嵌套的 setTimeout 相较于 setInterval 能够更精确地设置两次执行之间的延时。

// 代码解释例子：
function func(params) {
	// 这个函数要花费 1s 才能执行完成
}

setInterval(() => {
	// 这个函数没有执行完成前，调度器还是在继续计时执行。
	// 所以，第一个 func 刚执行完，就会立即进入下一个 func 的执行。
	// 因为下一次的调度时间已经过去了。
	func()
}, 100)

setTimeout(function tick() {
	func() // 会等待这个函数执行完后，才会进入下次计时调度。
	setTimeout(tick, 100)
}, 100)

// *********************************************************************************************

// * 垃圾回收和 setInterval/setTimeout 回调（callback）

setTimeout(function () {}, 100) // 在调度函数调用之前，这个匿名函数将一直存在于内存中。调度结束后才销毁。
// 对于 setInterval，传入的函数也是一直存在于内存中，直到 clearInterval 被调用。

// 如果函数引用了外部变量，那么只要调度函数存在，外部的变量也会随之存在，直到调度函数被清除。
// 它们可能占用函数更大的内存，所以当我们不需要调度函数时，最好手动取消。

// *********************************************************************************************

// * 零延时实际上不为零（在浏览器中）
// 根据 HTML5 规范所讲：经过 5 重嵌套定时器之后，后面的每个定时器，时间间隔被强制设定为 4 毫秒（第5层开始）。
// 使用 setInterval，也会发生类似的情况：setInterval(f) 会以零延时运行几次 f，然后以 4 毫秒以上的强制延时运行。

// 对于服务端的 JavaScript 就没有这个限制，并且还有其他调度即时异步任务的方式。这个只是针对浏览器环境的。

// *********************************************************************************************

// 总结：
// 所有的调度方法都不能 保证 确切的延时。

// 例如，浏览器内的计时器可能由于许多原因而变慢：

// CPU 过载。
// 浏览器页签处于后台模式。
// 笔记本电脑用的是省电模式。
// 所有这些因素，可能会将定时器的最小计时器分辨率（最小延迟）增加到 300ms 甚至 1000ms，具体以浏览器及其设置为准。
